package org.apache.ibatis.common.logging.jdbc;

import org.apache.ibatis.common.logging.Log;
import org.apache.ibatis.common.reflection.ExceptionUtil;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
import java.util.HashSet;
import java.util.Set;
import java.util.StringJoiner;

/**
 * @Name ResultSetLogger
 * @Description: [JDBC ResultSet 对象代理（日志增强）] 实现类
 * @Version v1.0.0
 * @Author: lichao
 * @Date: 2024-05-15 18:02:20
 **/
public class ResultSetLogger extends BaseJdbcLogger implements InvocationHandler {
    private final ResultSet rs;
    private int rows;
    private boolean first = true;
    private static final Set<Integer> BLOB_TYPES = new HashSet<>();
    private final Set<Integer> blobColumns = new HashSet<>();

    static {
        BLOB_TYPES.add(Types.BINARY);
        BLOB_TYPES.add(Types.VARBINARY);
        BLOB_TYPES.add(Types.BLOB);
        BLOB_TYPES.add(Types.CLOB);
        BLOB_TYPES.add(Types.NCLOB);
        BLOB_TYPES.add(Types.LONGVARCHAR);
        BLOB_TYPES.add(Types.LONGNVARCHAR);
        BLOB_TYPES.add(Types.LONGVARBINARY);
    }

    private ResultSetLogger(ResultSet rs, Log statementLog, int queryStack) {
        super(statementLog, queryStack);
        this.rs = rs;
    }

    public static ResultSet newInstance(ResultSet rs, Log statementLog, int queryStack) {
        InvocationHandler handler = new ResultSetLogger(rs, statementLog, queryStack);
        ClassLoader loader = ResultSet.class.getClassLoader();
        return (ResultSet) Proxy.newProxyInstance(loader, new Class[]{ResultSet.class}, handler);
    }

    public ResultSet getRs() {
        return rs;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            // 如果方法继承自 Object 类，交由当前对象执行
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }
            // 直接放行，获取执行结果，打印结果日志
            Object ret = method.invoke(rs, args);
            if ("next".equals(method.getName())) {
                if ((Boolean) ret) {
                    rows++;
                    if (isTraceEnabled()) {
                        ResultSetMetaData metaData = rs.getMetaData();
                        final int columnCount = metaData.getColumnCount();
                        if (first) {
                            first = false;
                            printColumnHeaders(metaData, columnCount);
                        }
                        printColumnValues(columnCount);
                    }
                } else debug("    Total: " + rows, false);
            }
            clearColumnInfo();
            return ret;
        } catch (Throwable e) {
            throw ExceptionUtil.unwrapThrowable(e);
        }
    }

    private void printColumnHeaders(ResultSetMetaData metaData, int columnCount) throws SQLException {
        StringJoiner row = new StringJoiner(", ", "   Columns: ", "");
        for (int i = 0; i < columnCount; i++) {
            if (BLOB_TYPES.contains(metaData.getColumnType(i))) blobColumns.add(i);
            row.add(metaData.getColumnLabel(i));
        }
        trace(row.toString(), false);
    }

    private void printColumnValues(int columnCount) {
        StringJoiner row = new StringJoiner(", ", "     Row: ", "");
        for (int i = 0; i < columnCount; i++) {
            try {
                if (blobColumns.contains(i)) row.add("<<BLOB>>");
                else row.add(rs.getString(i));
            } catch (SQLException e) {
                // 对于一个 BLOB 属性，无法调用 getString() 进行展示
                row.add("<<不能展示>>");
            }
        }
        trace(toString(), false);
    }
}

